package com.cyh;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock功能跟synchronized相似，加锁
 *    对比synchronize具有特点：
 *        可中断
 *        可以设置超时时间
 *        可以设置为公平锁，防止饥饿锁出现, 默认不公平，公平改成：new ReentrantLock(true). 公平指线程先到先得。 非公平指竞争得到锁
 *        支持多个条件变量
 *
 *    与synchronize一样，都是支持可重入的
 *
 *    可重入是指同一个线程如果首次获得了这把锁，那么它是这把锁的拥有者，因此有权利再次获得这把锁。
 *    如果是不可重入锁，那么第二次获得锁时，自己也会被锁挡住
 *
 *
 * JVM内存体现一下几方面
 *   原子性: synchronize
 *   可见性: volatile
 *   有序性： 指令的顺序不变： volatile
 *
 * 可见性：成员变量在线程外改变了，线程内是while(成员变量){}不会停止，因为在这个线程内有缓存， 多次读取成员变量，其实事实只读了一次缓存到线程缓存中。
 *        解决方法： 给成员变量加volatile , 修饰成员变量和静态变量
 *        synchronize也会改变可见性， 主线程改变变量前加synchronize, 子线程while里面也加，如while(true){synchronized(lockobject){if(!变量){break;}}}
 *        synchronize重，性能低， volatile轻，性能高
 *        volatile不能保证原子性， 适合一个线程写，多个线程读的情况
 *        synchronize: 适合多个线程写的情况
 *        System.out.println() 也能保证可见性
 *
 *volatile可以解决有序性的重排序的问题，比如：一个线程a=1;b=true, 编译可能是b=true;a=1; 走到b=true;然后切换线程if(b){a++};导致结果不是预期；所以volatile boolean b = true;
 *       原理：遇到volatile, 会将自己和之前的变化写入主存，或者从主存中重新获取。
 *
 *   序列化 public final class Singleton implements Serializable{}, 单例模式， final防止继承， Serializable可以序列化，构造函数private修饰。
 *   那如何做到反序列化返回的也是单例呢？解决方法：在类中加下面这个函数：public Object readResolve(){return INSTANCE} 其中INSTANCE是单例对象，表示反序列化返回的也是这个单例对象
 *
 * AtomicInteger 不加锁来实现整数的加减操作。 如果查询余额和取款操作的demo类：AccountCas,但是这个函数实现不加繁琐，使用封装方法可以更简单如下：
 *                   AtomicInteger.incrementAndGet();// ++i
 *                   AtomicInteger.getAndIncrement();// i++
 *                   AtomicInteger.addAndGet(5);// 先加5，再获取
 *                   AtomicInteger.getAndAdd(5);// 先获取，再加5
 *                   AtomicInteger.updateAndGet(value -> value * 10);
 *
 *               其他类型也有相应的原子类型
 *               适用于线程数少，多核CPU的场景下
 *               原子整数：AtomicInteger、AtomicInteger、AtomicLong
 *               原子引用类型：AtomicReference、AtomicMarkableReference、AtomicStampedReference
 *                         比如对大数类型BigDecimal进行原子封装demo类：DecimalAccountCas
 *                         AtomicStampedReference: 只要有其他线程改动过共享变量，那么自己的cas就算失败，并且知道修改了多少次，这时，仅比较值是不够的，需要再加一个版本号： demo:StampCas
 *                         AtomicMarkableReference是AtomicStampedReference简化版本，使用场景不需要知道修改的次数，只需要知道是否修改的情况。boolean值
 *               原子数组：AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 保护数组里面的元素的原子性
 *               字段更新器：AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater: 保护对象中的某个字段，字段必须volatile修饰。
 *               累加器：LongAdder 高效的i++操作
 *
 *
 * Unsafe对象提供了非常底层的，操作内存，线程的方法，Unsafe对象不能直接调用，只能通过反射获得。 demo:MyUnsafe.java: 线程安全处理数据
 *
 *
 * 日期转换：SimpleDateFormat线程不安全， DateTimeFormatter线程安全
 *
 * 线程池 public ThreadPoolExecutor(int corePoolSize,
 *                                 int maximumPoolSize,
 *                                 long keepAliveTime,
 *                                 TimeUnit unit,
 *                                 BlockingQueue<Runnable> workQueue,
 *                                 ThreadFactory threadFactory,
 *                                 RejectedExecutionHandler handler)
 *         参数：corePoolSize: 核心线程数目（最多保留的线程数）
 *              maximumPoolSize: 最大线程数目
 *              keepAliveTime: 生存时间-针对救急线程
 *              unit: 时间单位-针对救急线程
 *              workQueue: 阻塞队列
 *              threadFactory: 线程工厂-可以为线程创建时起好名字
 *              handler: 拒绝策略
 *         线程池里面有核心线程和救急线程：比如corePoolSize = 2, maximumPoolSize =3, 则核心线程是2，救急线程是1 （3 - 2） = 1
 *         再设置workQueue.size=2, 开始两个任务会到核心线程中执行， 再来两个任务会存到workQueue中，当第五个任务来了，会到救急线程中执行，第六个任务来了，就会拒绝并执行拒绝策略。
 *         救急线程执行完成后，超过keepAliveTime时间就会结束掉。
 *         拒绝策略jdk有4种：1. AbortPolicy:直接抛异常， 默认
 *                        2. CallerRunsPolicy: 让调用者运行任务
 *                        3. DiscardPolicy:放弃本次任务
 *                        4. DiscardOldestPolicy:抛弃最早的任务，并用本次任务代替
 *                        Dubbo的实现是抛异常之前记录日志
 *                        Netty创建新线程执行
 *                        ActiveMQ等待60秒尝试放入队列
 *                        PintPoint逐一尝试每种拒绝策略
 *        上面的创建太复杂，所以jdk提供几种工厂方法：
 *           1.newFixedThreadPool(int nThreads)
 *               new ThreadPoolExecutor(nThreads,
 *  *                                 nThreads,
 *  *                                 0,
 *  *                                 TimeUnit.MILLISECONDS,
 *  *                                 new LinkedBlockingQueue<Runnable>())
 *               特点：核心线程数==最大线程数（没有救急线程），因此无需超时时间
 *                    阻塞队列是无界的，可以放任意数量的任务
 *                    评价：适用于任务量已知，相对耗时的任务
 *           2.newCachedThreadPool()
 *               new ThreadPoolExecutor(0,
 *                                      Integer.MAX_VALUE,
 *                                      60,
 *                                      TimeUnit.SECONDS,
 *                                      new SynchronousQueue<Runnable>())
 *                特点：核心线程数是0， 最大线程数是Integer.MAX_VALUE, 救急线程空闲时间是60s.
 *                    全是救急线程，并且可以无限创建线程救急线程
 *                    适用于：任务数密集，执行时间较短时。
 *                SynchronousQueue队列的特点：他没有容量，没有线程来取就放不进去（一手交钱一手交货）， put进一个数后，线程阻塞，直到有其他线程取走了这个数，才会运行put后面的代码。
 *          3. newSingleThreadExecutor()
 *               new ThreadPoolExecutor(1,
 *                                      1,
 *                                      0,
 *                                      TimeUtil.MILLISECONDS,
 *                                      new LinkedBlockingQueue<Runnable>())
 *              特点： 核心线程数1，没有救急线程，阻塞队列是无界的
 *              适用于：多个任务排队执行
 *                 自己创建一个单线程串行执行任务，如果任务执行失败而终止那么没有任何补救措施，而单线程池还会新建一个线程，保证池的正常工作。
 *
 *     ThreadPoolExecutor方法：
 *         1. execute(Runnable command); 提交执行任务
 *         2. submit: 提交执行任务，带返回值， callable里面的是任务
 *            Future<String> future = pool.submit(new Callable<String>(){
 *                  @Override
 *                  public String call(){
 *                      Thread.sleep(1000);
 *                      return "ok";
 *                  }
 *            })
 *            String result = future.get(); //阻塞，1秒后返回结果ok
 *         3. List<Future<T>> invokeAll(Collection<? extends Callable<T> tasks) 提交tasks中的所有任务
 *         4. List<Future<T>> invokeAll(Collection<? extends Callable<T> tasks， long timeout, TimeUnit unit) 提交tasks中的所有任务, 带超时时间
 *           List<Future<String>> futures = pool.invokeAll(Arrays.asList(
 *              () -> {
 *                  Thread.sleep(1000);
 *                  return "1";
 *              },
 *              () -> {
 *                  Thread.sleep(1000);
 *  *                  return "2";
 *              }
 *           ));
 *           futures.forEach(f -> {
 *               String s = f.get(); //阻塞获取结果
 *           });
 *         5. T invokeAny(Collection<? extends Callable<T> tasks) 提交tasks的所有任务，执行最新得到结果的任务，并返回接口，其他任务取消
 *         6. T invokeAny(Collection<? extends Callable<T> tasks， long timeout, TimeUnit unit) 提交tasks的所有任务，执行最新得到结果的任务，并返回接口，其他任务取消，, 带超时时间
 *         7. shutdown 关闭线程池：不会接收新任务，但已提交任务会执行完，不能增加新任务，此方法不会阻塞调用线程的执行（即调用了shutdown, 马上运行下一句代码，不会等待）
 *         8. shutdownNow: 关闭线程池：不会接收新任务，也不能增加新任务，会将队列中的任务返回，并用interrupt的方法中断正在执行的任务
 *         9. isShutdown(): 是否非RUNNING状态
 *         10. isTerminated()： 是否终止了线程池
 *
 *   线程池导致饥饿死锁的问题：两个服务员技能点餐又能做菜，如果两个服务都去点餐了，就没人做菜了，导致饥饿。所以要点餐用一个线程池，做菜用另外一个线程池。
 *
 *   线程池中个数如何确定呢？
 *     方法一：CPU密集型运算：cpu利用率最高：  cpu核数 + 1
 *     方法二：I/O密集型运算，常用于业务计算：  我们最常用这种方式
 *           线程数 = 核数 * 期望CPU利用率 * 总时间（CPU计算时间 + 等待时间） / CPU计算时间
 *           如 8核CPU，利用率70%， 计算时间60%， 等待时间40%
 *           8 * 70% * （60% + 40%）/60%
 *
 *   任务调度线程池：
 *      方法一： Timer:优点：使用简单。缺点所有任务在一个线程中执行，多个任务会串行执行，前一个任务延时会影响后面的任务
 *             Timer timer = new Timer();
 *             TimerTask task1 = new TimerTask() {run()->执行耗时任务}
 *             TimerTask task2 = new TimerTask() {run()->执行耗时任务}
 *             timer.schedule(task1, 1000);
 *             timer.schedule(task2, 1000);
 *             1秒后执行任务1和任务2; 如果任务1中sleep(1000)， 那么要2秒才会运行任务2. 如果任务1有异常，任务2不会运行。
 *      方法二： ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); //2表示核心线程数
 *              pool.schedule(() -> {
 *                  System.out.println("task1");
 *                  Thread.sleep(1000)
 *              }, 1, TimeUnit.SECONDS);
 *              pool.schedule(() -> {
 *                      System.out.println("task1");
 *  *                  Thread.sleep(1000)
 *  *              }, 1, TimeUnit.SECONDS);
 *             1秒后，任务1和任务2同时执行； 但如果核心线程数改成1，则1秒后先执行task1, 再过1秒才执行任务2. 任务1出现异常，不会影响任务2.
 *             pool.scheduleAtFixedRate(() -> {
 *                 System.out.println("执行任务");
 *                 Thread.sleep(3000);
 *             }, 1, 2, TimeUnit.SECONDS); 延时1秒，定时每2秒执行任务， 如果执行任务的时间超过2秒，则定时时间变成执行的时间3秒。
 *             pool.scheduleWithFixedDelay(() -> {
 *  *                 System.out.println("执行任务");
 *  *                 Thread.sleep(3000);
 *  *             }, 1, 2, TimeUnit.SECONDS); 延时1秒，每间隔5秒执行一个任务，因为任务执行需要3秒，再延时2秒，总共5秒
 *
 *          线程池中的任务如果发生异常，不会在打印台上打印出来，一种解决方法是所有任务加try{}cath(){}
 *          另外一种是：Future<Boole> f = pool.submit(()->{
 *              int i = i/0;
 *              return true;
 *          }
 *          f.get();
 *          这时出现异常会打印出信息， 所以第二种方法是用submit方法
 *
 *
 *
 *
 *
 *
 * */
public class Test03 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String args[]) {
        //test01();
        //test02();
        test03();
    }

    //测试可重入的特性
    public static void test01() {
        lock.lock();
        try {
            System.out.println("------1");
            lock.lock();
            try {
                System.out.println("------2");
            } finally {
                lock.unlock();
            }
        }finally {
            lock.unlock();
        }

    }

    //打断锁
    public static void test02() {
        Thread t1 = new Thread(() -> {
            System.out.println("尝试获取锁, 这个锁可以打断");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("没有获得锁，需要返回");
                return;
            }
            try {
                System.out.println("获得锁");
            }finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();

        t1.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打断t1的锁");
        t1.interrupt(); //只能打断lockInterruptibly()锁， 无法打断lock()锁

    }

    //超时锁
    public static void test03() {
        Thread t1 = new Thread(() -> {
            System.out.println("尝试获取锁, 这个锁可以打断");
            try {
                //if(!lock.tryLock()) {
                if(!lock.tryLock(2, TimeUnit.SECONDS)) { //加了超时时间
                    System.out.println("获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("没有获得锁，需要返回");
                return;
            }
            try {
                System.out.println("获得锁");
            }finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("主线程获得锁");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程释放锁");
        lock.unlock();

    }

    //可以设置多个条件， 对不同的条件单独唤醒
    static Condition waitCigaretteSet = lock.newCondition(); //条件一 休息室
    static Condition waitTakeoutSet = lock.newCondition(); //条件二  休息室
    public static boolean hasCigarette= false;
    //多变量
    public static void test04() {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("有烟没？" + hasCigarette);
                //if(!hasCigarette) {
                while(!hasCigarette) { //如果是外卖和烟同时等待时，都要用while代替if
                    System.out.println("没烟，先等一会");
                    try {
                        waitCigaretteSet.await(); //等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("现在有烟码？" + hasCigarette);
                if(hasCigarette) {
                    System.out.println("可以开始抽烟了" );
                }
            } finally {
                lock.unlock();
            }
        }, "小南").start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        waitCigaretteSet.signal(); //唤醒

    }
}
